{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4a8cff59",
   "metadata": {},
   "source": [
    "Build a tool that takes a technical question,  \n",
    "and responds with an explanation. This is a tool that you will be able to use yourself during the course!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "f33b7be3",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from dotenv import load_dotenv\n",
    "from IPython.display import Markdown, display\n",
    "from openai import OpenAI\n",
    "import requests"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "51a266e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# constants\n",
    "\n",
    "MODEL_GPT = 'gpt-4o-mini'\n",
    "MODEL_LLAMA = 'llama3.2'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "40f6a368",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "API key found and looks good so far!\n"
     ]
    }
   ],
   "source": [
    "load_dotenv(override=True)\n",
    "api_key = os.getenv('OPENAI_API_KEY')\n",
    "\n",
    "# Check the key\n",
    "\n",
    "if not api_key:\n",
    "    print(\"No API key was found - please head over to the troubleshooting notebook in this folder to identify & fix!\")\n",
    "elif not api_key.startswith(\"sk-proj-\"):\n",
    "    print(\"An API key was found, but it doesn't start sk-proj-; please check you're using the right key - see troubleshooting notebook\")\n",
    "elif api_key.strip() != api_key:\n",
    "    print(\"An API key was found, but it looks like it might have space or tab characters at the start or end - please remove them - see troubleshooting notebook\")\n",
    "else:\n",
    "    print(\"API key found and looks good so far!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "a6ae2857",
   "metadata": {},
   "outputs": [],
   "source": [
    "system_prompt=\"\"\"\n",
    "You are a teachnical tutor. Your responsibility is to help the user understand the technical code provided assuming they have no prior knowledge of the subject.\n",
    "\"\"\"\n",
    "\n",
    "user_prompt=\"\"\"\n",
    "Here is the code provided by the user, please explain what the code does and why.\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cb7e136e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def explain_code(code):\n",
    "    client = OpenAI(api_key=api_key)\n",
    "    #user_prompt = f\"Here is the code provided by the user: {code}\"\n",
    "    response = client.chat.completions.create(\n",
    "        model=MODEL_GPT,\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": system_prompt},\n",
    "            {\"role\": \"user\", \"content\": user_prompt + code}\n",
    "        ]\n",
    "    )\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "bccb2500",
   "metadata": {},
   "outputs": [],
   "source": [
    "def pretty_explanation(code):\n",
    "    explanation = explain_code(code)\n",
    "    display(Markdown(explanation))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "da07481c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Certainly! Let\\'s break down the provided code step by step. \\n\\n### Code Explanation\\n\\n1. **`{ ... for book in books if book.get(\"author\")}`**:\\n    - This part is a set comprehension in Python.\\n    - It iterates over a collection named `books` (which we can assume is a list or similar iterable containing dictionary items where each dictionary represents a book).\\n    - For each `book`, it checks if there is an author by using the method `book.get(\"author\")`. The `get` method retrieves the value associated with the key `\"author\"` in the `book` dictionary. If the key doesn\\'t exist, `get` returns `None`, which is considered False in a boolean context.\\n    - If the `book` has an `\"author\"`, it collects that author\\'s value into a set. Using a set automatically handles duplicates — i.e., if multiple books are by the same author, the author will only appear once in the resulting set.\\n\\n2. **`yield from {...}`**:\\n    - The `yield from` expression is used in Python generator functions. It allows the generator to yield all values from an iterable, which in this case is the set created above.\\n    - By using `yield from`, the function can yield each author found in the set one by one, effectively returning them as if it was yielding them directly.\\n\\n### Overall Functionality\\n\\nSo, what the entire line does is:\\n\\n- It creates a set of unique authors (from the `books` list) by checking each book for an author and storing the author\\'s name if it exists. \\n- Then, it yields each unique author one by one to wherever this generator function is being called.\\n\\n### Why is this useful?\\n\\nThis code snippet can be particularly useful in scenarios where you want to extract a list of unique authors from a collection of books, perhaps to display them, process them further, or to ensure that you\\'re only working with one instance of each author name. It\\'s efficient because it uses a set to avoid duplicates and a generator to yield results without needing to store the whole list of authors in memory at once. \\n\\n### Example\\n\\nImagine you have the following `books` list:\\n\\n```python\\nbooks = [\\n    {\"title\": \"Book A\", \"author\": \"Author 1\"},\\n    {\"title\": \"Book B\", \"author\": \"Author 2\"},\\n    {\"title\": \"Book C\", \"author\": \"Author 1\"},\\n    {\"title\": \"Book D\"},\\n]\\n```\\n\\nRunning the given code would yield:\\n\\n```\\n\"Author 1\"\\n\"Author 2\"\\n```\\n\\nIt skips `Book D` because it doesn\\'t have an author, and it consolidates \"Author 1\" into a single entry.'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "explain_code('yield from {book.get(\"author\") for book in books if book.get(\"author\")}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0f346c85",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "This line of code is using Python and contains multiple concepts such as generator expressions, set comprehensions, and conditional filtering. Let's break it down step by step.\n",
       "\n",
       "### Code Breakdown\n",
       "\n",
       "1. **Set Comprehension**: \n",
       "   - The expression `{book.get(\"author\") for book in books if book.get(\"author\")}` is a set comprehension. This means it's creating a set, which is a collection of unique items. \n",
       "   - The set is constructed by iterating over `books`, which is assumed to be a list (or any iterable) of book dictionaries.\n",
       "\n",
       "2. **Iterating Through Books**:\n",
       "   - `for book in books`: This part of the code means you are going through each `book` in the `books` collection.\n",
       "\n",
       "3. **Getting the Author**:\n",
       "   - `book.get(\"author\")`: This retrieves the value associated with the key `\"author\"` in the current `book` dictionary. \n",
       "   - The `get` method is used to safely access dictionary keys: if the key doesn’t exist, it will return `None` instead of raising a KeyError.\n",
       "\n",
       "4. **Conditional Filtering**:\n",
       "   - `if book.get(\"author\")`: This condition checks if the author exists and is not `None`. If it exists, it gets included in the set being created; if not, it is skipped.\n",
       "\n",
       "5. **Yielding from the Set**:\n",
       "   - `yield from`: This is a special Python statement used in a generator function. It allows the function to yield all values from an iterable (in this case, the set of authors). \n",
       "   - This means that instead of collecting all authors into a list and returning it at once, the generator function will yield authors one by one as they are requested.\n",
       "\n",
       "### What Does The Code Do?\n",
       "\n",
       "- **Purpose**: The code constructs a set of unique authors from a list of books (where each book is represented as a dictionary). It then yields each author one at a time to the caller.\n",
       "- **Why It's Useful**: This allows you to get a stream of authors without generating a whole list in memory, which can be beneficial if you are working with a large dataset and want to save memory.\n",
       "\n",
       "### Example:\n",
       "\n",
       "If you had a list like this:\n",
       "\n",
       "```python\n",
       "books = [\n",
       "    {\"title\": \"Book 1\", \"author\": \"Author A\"},\n",
       "    {\"title\": \"Book 2\", \"author\": \"Author B\"},\n",
       "    {\"title\": \"Book 3\", \"author\": None},\n",
       "    {\"title\": \"Book 4\", \"author\": \"Author A\"},\n",
       "]\n",
       "```\n",
       "\n",
       "The line of code would create a set containing `{\"Author A\", \"Author B\"}` because \"Author A\" appears more than once but is only included once in the set, and the book with no author is ignored.\n",
       "\n",
       "In summary, this code efficiently collects unique authors from a potentially large list of books and provides them one at a time, which is handy in various applications, especially when dealing with large data."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "pretty_explanation('yield from {book.get(\"author\") for book in books if book.get(\"author\")}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "49538be3",
   "metadata": {},
   "outputs": [],
   "source": [
    "requests.get(\"http://localhost:11434/\").content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "68db9b94",
   "metadata": {},
   "outputs": [],
   "source": [
    "ollama_url = \"http://localhost:11434/v1\"\n",
    "ollama = OpenAI(api_key=\"ollama\", base_url=ollama_url)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "38507f39",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "It looks like we have a bit of both here!\n",
       "\n",
       "The first part asks for the capital of France.\n",
       "\n",
       "**Capital of France:**\n",
       "The capital of France is \"Paris\".\n",
       "\n",
       "Now, let's break down the code:\n",
       "\n",
       "```python\n",
       "print('\n",
       "'.join('* ' * i for i in range(1, 5 + 1)))\n",
       "```\n",
       "\n",
       "This code uses a few Python concepts, but don't worry, I'll explain it step by step.\n",
       "\n",
       "**Syntax**: `print()`\n",
       "The `print()` function is used to output text or values to the screen. In this case, we're using it to print out the result of the expression inside the parentheses.\n",
       "\n",
       "**Concatenation operator**: `'`. The dot symbol (`.`) is often used as a concatenation operator, meaning you can chain strings together with it.\n",
       "The variable `print()` stores the return value of the function that precedes it. In this case, we're passing some string values to be printed out.\n",
       "\n",
       "***`: Multiplication operator\n",
       "`*` has multiple meanings in Python:\n",
       "1. **Multiplication:** When used as a multiplication operator, it can multiply numbers.\n",
       "2. **String formatting (F-string):** When used with curly brackets `{}`, `*` is an asterisk for F-strings.\n",
       "\n",
       "In this code, we use `* i` to repeat the string `' '*` a number of times equivalent to `i`, where `i` is each number in the loop's range.\n",
       "\n",
       "### Code Breakdown\n",
       "```python\n",
       "print('(':\n",
       "```\n",
       "Prints just '('.\n",
       "```print(')'.join(*['']) \n",
       "# [empty string]: single-element list containing nothing\n",
       "```The expression `\"'.join('*' * i for i in range(1, 5 + 1))`:\n",
       "``` \n",
       "   -   Loop the numbers from `1` to `4` (including `4`) with `range(1,5+1)= range(1,6)`\n",
       "   -\t For each number in the loop, create an F-string (`\"*'*i*\"\"`) by repeating string `\" \"*`, i.e., string `' '*` is repeated `i` times.\n",
       "   -  Put them into a list (`[\"\"*1\",\"\"*2\",\"[*3\"] etc.`.)\n",
       "   -  Pass that single-element list to the join() function on the string `\"'.\")`\n",
       "            So `\"'.*(*\"join('\n",
       "       *        *          \"\"*        *\"      \"     *\"  \"\n",
       "    )` will result in: `*' '`\n",
       "\n",
       "In summary, this code prints `'*( \"*\"*\"*'*\"'\"*''*')`.\n",
       " \n",
       "\n",
       "In conclusion:\n",
       "- The capital of France is Paris.\n",
       "- The string `'* '*`: will print a horizontal line with each asterisks as the spacing between."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "tutor_prompt = user_prompt + \"print('\\n'.join('* ' * i for i in range(1, 5 + 1)))\"\n",
    "response = ollama.chat.completions.create(model=MODEL_LLAMA, messages=[\n",
    "            {\"role\": \"system\", \"content\": system_prompt},\n",
    "            {\"role\": \"user\", \"content\": tutor_prompt}\n",
    "        ])\n",
    "display(Markdown(response.choices[0].message.content))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9ca0c25",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
